import 'reflect-metadata'
import { reactive } from 'vue'
import {
  METHOD_TYPE,
  METHOD_TYPE_ACTION,
  METHOD_TYPE_GETTER,
  METHOD_TYPE_MUTATION,
  MODULE_NAME
} from './constant'
import { Module, Getter, Mutation, Action } from './decorator'
import type { TFunction } from './type'

export class StoreModule {}

let store: any

export function createStore() {
  store = reactive({})
  return store
}

export function createStoreModule<T extends StoreModule>(
  target: TFunction<T>
): T {
  const moduleName: string = Reflect.getMetadata(MODULE_NAME, target)
  const obj: any = new target()
  store[moduleName] = obj

  const result: any = {}
  // define properties
  const properties = Object.keys(obj)
  for (const property of properties) {
    Object.defineProperty(result, property, {
      get() {
        return store[moduleName][property]
      }
    })
  }
  // define methods
  const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(obj))
  for (const method of methods) {
    if (method === 'constructor') continue
    const type = Reflect.getMetadata(METHOD_TYPE, target.prototype, method)
    // define getters
    if (type === METHOD_TYPE_GETTER) {
      Object.defineProperty(result, method, {
        get() {
          return store[moduleName][method]
        }
      })
    } // define mutations
    else if (type === METHOD_TYPE_MUTATION) {
      Object.defineProperty(result, method, {
        value(...args: any) {
          obj[method].call(store[moduleName], ...args)
        }
      })
    } // define actions
    else if (type === METHOD_TYPE_ACTION) {
      Object.defineProperty(result, method, {
        value(...args: any) {
          return new Promise((resolve) => {
            resolve(obj[method].call(store[moduleName], ...args))
          })
        }
      })
    }
  }
  return result
}

export { Module, Getter, Mutation, Action }
